/*    
This file is part of jME Planet Demo.

jME Planet Demo is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation.

jME Planet Demo is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Lesser General Public License for more details.

You should have received a copy of the GNU General Public License
along with jME Planet Demo.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.ankh.unfall.planet;

import com.jme3.asset.AssetManager;
import com.jme3.asset.plugins.FileLocator;
import com.jme3.light.DirectionalLight;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.FastMath;
import com.jme3.math.Vector3f;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.shape.Sphere;
import com.jme3.texture.Image;
import com.jme3.texture.Texture;
import com.jme3.texture.Texture2D;
import com.jme3.util.TangentBinormalGenerator;
import java.nio.ByteBuffer;
import java.util.LinkedList;

import org.ankh.unfall.planet.texgen.PlanetGenerator;

/**
 * Telluric planet object
 * @author Yacine Petitprez (anykeyh)
 */
public class Planet extends Node
{
    private Geometry planetGeometry;
    /*   private RenderPass atmoBackRenderPass, atmoFrontRenderPass, planetRenderPass;
    private CullState backFaceCullingState;
    private CullState frontFaceCullingState;
    private AlphaState alphaBlendingState;
    private ZBufferState zbufferEnabledState;
    private GLSLShaderObjectsState planetShader;
    private GLSLShaderObjectsState atmoShader;*/
    private float counter;
    private PlanetInformations informations;
    private PlanetGenerator generator;
    private int textureHeight, textureWidth;
    /*
     * TODO: Delete this:
     * LinkedList for texture maps for development
     */
    public LinkedList<Texture2D> maps;

    //XXX: COMING SOON
    //private List<Planet> moons;
    //private Sun sun;
    private Image getBaseMapData()
    {
        ByteBuffer baseMapData = ByteBuffer.allocateDirect(textureWidth * textureHeight * 4);
        int[] colorMap = generator.getColorMap();

        // For each color int in the colormap:
        for (int c : colorMap)
        {
            //ARGB -> RGBA
            //= (ARGB & 0x00FFFFFF) << 8 | 0xFF;
            int newc = ((c & 0x00FFFFFF) << 8) | 0xFF;
            baseMapData.putInt(newc);
        }

        Image baseMap = new Image(Image.Format.RGBA8, textureWidth, textureHeight, baseMapData);

        return baseMap;
    }

    private Image getHeightMapData()
    {
        ByteBuffer heightMapData = ByteBuffer.allocateDirect(textureWidth * textureHeight * 4);
        int[] heightMap = generator.getHeightMap();

        // For each color int in the colormap:
        for (int c : heightMap)
        {
            //ARGB -> RGBA
            //= (ARGB & 0x00FFFFFF) << 8 | 0xFF;
            int newc = ((c & 0x00FFFFFF) << 8) | 0xFF;
            heightMapData.putInt(newc);
        }

        Image outputMap = new Image(Image.Format.RGBA8, textureWidth, textureHeight, heightMapData);

        return outputMap;
    }

    private Image getSpecularMapData()
    {
        ByteBuffer baseMapData = ByteBuffer.allocateDirect(textureWidth * textureHeight * 4);
        int[] colorMap = generator.getSpecularMap();

        for (int c : colorMap)
        {
            int newc = ((c & 0x00FFFFFF) << 8) | 0xFF;
            baseMapData.putInt(newc);
        }

        Image baseMap = new Image(Image.Format.RGBA8, textureWidth, textureHeight, baseMapData);

        return baseMap;
    }

    private Image getNormalMapData()
    {
        ByteBuffer baseMapData = ByteBuffer.allocateDirect(textureWidth * textureHeight * 4);
        int[] colorMap = generator.getNormalMap();

        for (int c : colorMap)
        {
            int newc = ((c & 0x00FFFFFF) << 8) | 0xFF;
            baseMapData.putInt(newc);
        }

        Image baseMap = new Image(Image.Format.RGBA8, textureWidth, textureHeight, baseMapData);

        return baseMap;
    }

    /**
     * Update the static (non calculated each frame) uniforms
     */
    private void updateStaticUniforms()
    {

        /* PLANET OBJECT ---------------------------------------------------- */
        //XXX Light uniforms will be configured when I'll make the sun object
       /* planetShader.setUniform("fvSpecular", ColorRGBA.White);
        planetShader.setUniform("fvDiffuse", ColorRGBA.White);
        
        planetShader.setUniform("fSpecularPower", 30.0f);
        
        planetShader.setUniform("fCloudHeight", informations.getCloudHeight());
        
        planetShader.setUniform("baseMap", 0);
        planetShader.setUniform("normalMap", 1);
        planetShader.setUniform("specMap", 2);
        if (informations.hasCloud())
        {
        planetShader.setUniform("cloudsMap", 3);
        } else
        {
        //pointer to noTextures
        planetShader.setUniform("cloudsMap", 4);
        }*/

        /* ATMO OBJECT ---------------------------------------------------- */

        /*
        atmoShader.setUniform("fAbsPower", informations.getAtmosphereAbsorptionPower());
        
        atmoShader.setUniform("fAtmoDensity", informations.getAtmosphereDensity());
        atmoShader.setUniform("fGlowPower", informations.getAtmosphereGlowPower());
        
        atmoShader.setUniform("fvAtmoColor", informations.getAtmosphereColor());
        //The light which comes on atmosphere
        atmoShader.setUniform("fvDiffuse", ColorRGBA.White);*/
    }

    /*  @Override
    public void updateRenderState()
    {
    super.updateRenderState();
    
    //Updating static uniforms:
    updateStaticUniforms();
    }
    
    @Override
    public void draw(Renderer r)
    {
    
    //XXX Light uniforms will be configured when I'll make the sun object
    Vector3f lightPosition = new Vector3f(0, 0, 25000);
    
    //On transforme la position de la lumiere vers le repere de la camera
    Vector3f lightPositionInModelView = new Vector3f();
    
    lightPositionInModelView.set(
    -lightPosition.dot(r.getCamera().getLeft()),
    lightPosition.dot(r.getCamera().getUp()),
    -lightPosition.dot(r.getCamera().getDirection()));
    
    planetShader.setUniform("fvLightPosition", lightPositionInModelView);
    
    planetShader.setUniform("fCloudRotation", counter * 0.0001f);
    
    atmoShader.setUniform("fvLightPosition", lightPositionInModelView);
    
    planetRenderPass.renderPass(r);
    atmoFrontRenderPass.renderPass(r);
    atmoBackRenderPass.renderPass(r);
    }*/
    /**
     * Construit l'objet 3D d'une planete
     * @param informations Les informations sur la planete.
     * @param generator Le générateur de texture utilisé. Si les texture ne sont pas encore généré, le constructeur s'occupera de le faire. 
     * @param renderer Couche de rendue employée
     */
    public Planet(PlanetInformations informations, PlanetGenerator generator, AssetManager assetManager)
    {

        this.informations = informations;
        this.generator = generator;

        textureHeight = generator.getHeight();
        textureWidth = generator.getWidth();

        /* Recuperation des textures */
        Texture baseMap = new Texture2D();
        baseMap.setImage(getBaseMapData());

        Texture specularMap = new Texture2D();
        specularMap.setImage(getSpecularMapData());

        Texture normalMap = new Texture2D();
        normalMap.setImage(getNormalMapData());

        Texture heightMap = new Texture2D();
        heightMap.setImage(getHeightMapData());

        Texture cloudMap = assetManager.loadTexture("Textures/clouds.dds");
        cloudMap.setWrap(Texture.WrapMode.Repeat);


        /*
         * TODO: Delete this
         * Stuff for displaying textures
         */
        maps = new LinkedList<Texture2D>();
        maps.add((Texture2D) baseMap.clone());
        maps.add((Texture2D) specularMap.clone());
        maps.add((Texture2D) normalMap.clone());
        maps.add((Texture2D) heightMap.clone());
        maps.add((Texture2D) cloudMap.clone());


        /* Mise en place des textures */
        /*  TextureState textureState = renderer.createTextureState();
        textureState.setTexture(baseMap, 0);
        textureState.setTexture(normMap, 1);
        textureState.setTexture(specularMap, 2);
        textureState.setTexture(cloudMap, 3);
        
        textureState.setEnabled(true);
        
        this.setRenderState(textureState);
        
        this.setLightCombineMode(LightState.OFF);
        
        // Creating each RenderStates of planet and atmosphere 
        zbufferEnabledState = renderer.createZBufferState();
        zbufferEnabledState.setFunction(ZBufferState.CF_LESS);
        zbufferEnabledState.setEnabled(true);
        
        
        backFaceCullingState = renderer.createCullState();
        backFaceCullingState.setCullMode(CullState.CS_BACK);
        backFaceCullingState.setEnabled(true);
        
        frontFaceCullingState = renderer.createCullState();
        frontFaceCullingState.setCullMode(CullState.CS_FRONT);
        frontFaceCullingState.setEnabled(true);
        
        alphaBlendingState = renderer.createAlphaState();
        alphaBlendingState.setDstFunction(AlphaState.SB_ONE_MINUS_SRC_ALPHA);
        alphaBlendingState.setSrcFunction(AlphaState.DB_SRC_ALPHA);
        alphaBlendingState.setBlendEnabled(true);
        alphaBlendingState.setEnabled(true);
        
        planetShader = renderer.createGLSLShaderObjectsState();
        planetShader.load(getClass().getResource("/org/ankh/unfall/media/shaders/planet.vert.glsl"),
        getClass().getResource("/org/ankh/unfall/media/shaders/planet.frag.glsl"));
        planetShader.setEnabled(true);
        
        atmoShader = renderer.createGLSLShaderObjectsState();
        atmoShader.load(getClass().getResource("/org/ankh/unfall/media/shaders/atmosphere.vert.glsl"),
        getClass().getResource("/org/ankh/unfall/media/shaders/atmosphere.frag.glsl"));
        atmoShader.setEnabled(true);
        
        
        
        
        
        
        // Creating each render pass: planet, atmosphere back face & atmosphere front face 
        planetRenderPass = new RenderPass();
        planetRenderPass.add(planetGeometry);
        planetRenderPass.setPassState(zbufferEnabledState);
        planetRenderPass.setPassState(textureState);
        planetRenderPass.setPassState(backFaceCullingState);
        planetRenderPass.setPassState(planetShader);
        
        atmoFrontRenderPass = new RenderPass();
        atmoFrontRenderPass.add(planetGeometry);
        atmoFrontRenderPass.setPassState(zbufferEnabledState);
        atmoFrontRenderPass.setPassState(frontFaceCullingState);
        atmoFrontRenderPass.setPassState(alphaBlendingState);
        atmoFrontRenderPass.setPassState(atmoShader);
        
        atmoBackRenderPass = new RenderPass();
        atmoBackRenderPass.add(planetGeometry);
        atmoBackRenderPass.setPassState(zbufferEnabledState);
        atmoBackRenderPass.setPassState(backFaceCullingState);
        atmoBackRenderPass.setPassState(alphaBlendingState);
        atmoBackRenderPass.setPassState(atmoShader);*/

        //planetGeom = new GeoSphere("Test", false, 5);
        //planetGeom.setLocalScale(Constants.kilometersToUnity(informations.getRadius()));
        Sphere planetSphere = new Sphere(64, 64, informations.getRadius());
        planetSphere.setTextureMode(Sphere.TextureMode.Projected);
        TangentBinormalGenerator.generate(planetSphere);
        planetGeometry = new Geometry("PlanetSphere", planetSphere);
        Material mat = new Material(assetManager, "MatDefs/PlanetMaterial.j3md");
        mat.setTexture("baseMap", baseMap);
        mat.setTexture("NormalMap", normalMap);
        mat.setTexture("SpecularMap", specularMap);
        mat.setBoolean("UseMaterialColors", true);
        mat.setColor("Specular", ColorRGBA.White);
        mat.setColor("Diffuse", ColorRGBA.White);
        mat.setFloat("Shininess", 5f);

        planetGeometry.setMaterial(mat);
        this.attachChild(planetGeometry);

        DirectionalLight sun = new DirectionalLight();
        sun.setColor(ColorRGBA.White);
        sun.setDirection(new Vector3f(0f, 0f, -1f).normalizeLocal());
        addLight(sun);
        /* Rotate the planet to have the poles on Y axis */
        getLocalRotation().fromAngleAxis(-FastMath.HALF_PI, Vector3f.UNIT_X);

        /* Day/night rotation */
        /* SpatialTransformer trans = new SpatialTransformer(1);
        trans.setObject(planetGeometry, 0, 0);
        trans.setRepeatType(SpatialTransformer.RT_WRAP);
        
        trans.setSpeed(1.f / informations.getDaytime());
        trans.setRotation(0, 0, new Quaternion().fromAngleAxis(0, Vector3f.UNIT_Z));
        trans.setRotation(0, 0.5f, new Quaternion().fromAngleAxis(FastMath.PI, Vector3f.UNIT_Z));
        trans.setRotation(0, 1.0f, new Quaternion().fromAngleAxis(FastMath.TWO_PI, Vector3f.UNIT_Z));
        
        trans.setActive(true);
        planetGeometry.addController(trans);*/

        //  this.setModelBound(new BoundingSphere());

        /* Object state updating */

    }

    /*  @Override
    public void updateWorldData(float time)
    {
    super.updateWorldData(time);
    counter += time; //update counter for shaders
    }*/
    
    public Planet(String surfaceTextureLocation, String normalMapLocation, String specularMapLocation, String cloudTextureLocation, String glowMapLocation, AssetManager assetManager, float radius){
        Texture baseMap;
        Texture normalMap;
        Texture specularMap;
        Texture cloudMap;
        Texture glowMap;
        /*
         * TODO: Delete this
         * Stuff for displaying textures
         */
        maps = new LinkedList<Texture2D>();
        
        Sphere planetSphere = new Sphere(64, 64, radius);
        planetSphere.setTextureMode(Sphere.TextureMode.Projected);
        TangentBinormalGenerator.generate(planetSphere);
        planetGeometry = new Geometry("PlanetSphere", planetSphere);
        Material mat = new Material(assetManager, "MatDefs/PlanetMaterial.j3md");
        mat.setBoolean("UseMaterialColors",true);    
        mat.setColor("Specular",ColorRGBA.White);
        mat.setColor("Diffuse",ColorRGBA.White);
        mat.setFloat("Shininess", 5f);
        
        
        if(surfaceTextureLocation !=null && !surfaceTextureLocation.equals(""))
        {
            assetManager.registerLocator(Planet.getFolder(surfaceTextureLocation), FileLocator.class);
            baseMap = assetManager.loadTexture(Planet.getFile(surfaceTextureLocation));
            mat.setTexture("baseMap", baseMap);
            maps.add((Texture2D)baseMap.clone());
        }
        if(normalMapLocation !=null && !normalMapLocation.equals(""))
        {
            assetManager.registerLocator(Planet.getFolder(normalMapLocation), FileLocator.class);
            normalMap = assetManager.loadTexture(Planet.getFile(normalMapLocation));
            maps.add((Texture2D)normalMap.clone());
            mat.setTexture("NormalMap", normalMap);
        }
        if(specularMapLocation !=null && !specularMapLocation.equals(""))
        {
            assetManager.registerLocator(Planet.getFolder(specularMapLocation), FileLocator.class);
            specularMap = assetManager.loadTexture(Planet.getFile(specularMapLocation));
            maps.add((Texture2D)specularMap.clone());
            mat.setTexture("SpecularMap", specularMap);
        }
        if(cloudTextureLocation !=null && !cloudTextureLocation.equals(""))
        {
            assetManager.registerLocator(Planet.getFolder(cloudTextureLocation), FileLocator.class);
            cloudMap = assetManager.loadTexture(Planet.getFile(cloudTextureLocation));
            maps.add((Texture2D)cloudMap.clone());
        }
        if(glowMapLocation !=null && !glowMapLocation.equals(""))
        {
            assetManager.registerLocator(Planet.getFolder(glowMapLocation), FileLocator.class);
            glowMap = assetManager.loadTexture(Planet.getFile(glowMapLocation));
            maps.add((Texture2D)glowMap);
            mat.setTexture("GlowMap", glowMap);
        }
        
        planetGeometry.setMaterial(mat);
        this.attachChild(planetGeometry);

        DirectionalLight sun = new DirectionalLight();
        sun.setColor(ColorRGBA.White);
        sun.setDirection(new Vector3f(0f, 0f, -1f).normalizeLocal());
        addLight(sun);
        /* Rotate the planet to have the poles on Y axis */
        getLocalRotation().fromAngleAxis(-FastMath.HALF_PI, Vector3f.UNIT_X);


    }
    
    private static String getFolder(String fileLocation)
    {
        return fileLocation.substring(0, fileLocation.lastIndexOf("/") + 1);
    }
    
    private static String getFile(String fileLocation)
    {
        return fileLocation.substring(fileLocation.lastIndexOf("/") + 1);
    }
}
